﻿using System;
using System.Diagnostics.Contracts;
using System.Threading;

namespace Rxx
{
	internal sealed class AsyncResult<T> : IAsyncResult, IDisposable
	{
		#region Public Properties
		public T Data
		{
			get
			{
				return data;
			}
		}

		public object OutOfBandData
		{
			get;
			set;
		}

		public object AsyncState
		{
			get
			{
				return state;
			}
		}

		public bool CompletedSynchronously
		{
			get;
			private set;
		}

		public bool IsCompleted
		{
			get;
			private set;
		}

		public WaitHandle AsyncWaitHandle
		{
			get
			{
				Contract.Ensures(Contract.Result<WaitHandle>() != null);

				lock (gate)
				{
					if (wait == null)
					{
						wait = new ManualResetEvent(IsCompleted);
					}

					return wait;
				}
			}
		}
		#endregion

		#region Private / Protected
		private readonly object gate = new object();
		private readonly object state;
		private ManualResetEvent wait;
		private T data;
		#endregion

		#region Constructors
		public AsyncResult(object state = null)
		{
			this.state = state;
		}
		#endregion

		#region Methods
		[ContractInvariantMethod]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
		private void ObjectInvariant()
		{
			Contract.Invariant(gate != null);
		}

		public void Complete(T resultData, bool synchronous)
		{
			lock (gate)
			{
				if (IsCompleted)
					throw new InvalidCastException();

				data = resultData;

				CompletedSynchronously = synchronous;
				IsCompleted = true;

				if (wait != null)
				{
					wait.Set();
				}
			}
		}

		public void Wait()
		{
			lock (gate)
			{
				if (IsCompleted)
				{
					return;
				}

				if (wait == null)
				{
					wait = new ManualResetEvent(false);
				}
			}

			Contract.Assume(wait != null);

			wait.WaitOne();
		}

		public void Dispose()
		{
			if (wait != null)
			{
				wait.Dispose();
			}
		}
		#endregion
	}
}